<template>
  <div
    id="my-three"
    ref="screenDom"
  ></div>
</template>
<script setup lang="ts">
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { onMounted, onUnmounted, ref, Ref } from 'vue'

let screenDom: Ref<any> = ref(null)
let scene: any, camera: any, renderer: any
let model = null
let clock = new THREE.Clock()
let mixer: any

// 渲染器开启阴影渲染：renderer.shadowMapEnabled = true;
// 灯光需要开启“引起阴影”：light.castShadow = true;
// 物体需要开启“引起阴影”和“接收阴影”：mesh.castShadow = mesh.receiveShadow = true;

function init() {
  scene = new THREE.Scene()
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
  renderer = new THREE.WebGLRenderer()
  // position and point the camera to the center of the scene
  camera.position.set(5, 5, 5)
  camera.lookAt(scene.position)

  // 增加坐标系红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
  // 添加坐标系到场景中
  const axes = new THREE.AxesHelper(20)
  scene.add(axes)

  // 调整背景颜色，边界雾化
  scene.background = new THREE.Color(0xa0a0a0)
  scene.fog = new THREE.Fog(0xa0a0a0, 10, 30)

  // 半球形光源
  const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444)
  hemiLight.position.set(0, 10, 0)
  scene.add(hemiLight)

  // 创建一个虚拟的球形网格 Mesh 的辅助对象来模拟 半球形光源 HemisphereLight.
  const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5)
  scene.add(hemiLighthelper)

  // 地面
  const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }))
  mesh.rotation.x = -Math.PI / 2
  mesh.receiveShadow = true
  scene.add(mesh)

  // 平行光
  const directionalLight = new THREE.DirectionalLight(0xffffff)
  directionalLight.castShadow = true
  directionalLight.shadow.camera.near = 0.5
  directionalLight.shadow.camera.far = 50
  directionalLight.shadow.camera.left = -10
  directionalLight.shadow.camera.right = 10
  directionalLight.shadow.camera.top = 10
  directionalLight.shadow.camera.bottom = -10
  directionalLight.position.set(0, 5, 5)
  scene.add(directionalLight)

  // 用于模拟场景中平行光 DirectionalLight 的辅助对象. 其中包含了表示光位置的平面和表示光方向的线段.
  const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5)
  scene.add(directionalLightHelper)

  renderer.shadowMap.enabled = true
  renderer.setSize(window.innerWidth, window.innerHeight)
  screenDom.value.appendChild(renderer.domElement)

  // 控制器
  const controls = new OrbitControls(camera, renderer.domElement)
}

function loadModel() {
  // 加载模型并开启阴影和接受阴影
  const gltfLoader = new GLTFLoader()
  gltfLoader.setPath('./static/').load(
    'saber.glb',
    function (gltf) {
      gltf.scene.rotation.y = Math.PI
      console.log('gltf\ngltf', gltf)
      gltf.scene.scale.set(1, 1, 1)
      gltf.scene.traverse(function (object: any) {
        if (object.isMesh) {
          object.castShadow = true //阴影
          object.receiveShadow = true //接受别人投的阴影
        }
      })

      // 使用动画混合器及配置
      mixer = startAnimation(
        gltf.scene,
        gltf.animations,
        gltf.animations[1].name // animationName，这里是"Run"
      )

      scene.add(gltf.scene)
      model = gltf.scene
    },
    function (res) {
      // console.log(res.total, res.loaded)
    }
  )
}

/**
 * 启动特定网格对象的动画。在三维模型的动画数组中按名称查找动画
 * @param skinnedMesh {THREE.SkinnedMesh} 要设置动画的网格
 * @param animations {Array} 数组，包含此模型的所有动画
 * @param animationName {string} 要启动的动画的名称
 * @return {THREE.AnimationMixer} 要在渲染循环中使用的混合器
 */
function startAnimation(skinnedMesh: any, animations: any, animationName: any) {
  const m_mixer = new THREE.AnimationMixer(skinnedMesh)
  const clip = THREE.AnimationClip.findByName(animations, animationName)

  if (clip) {
    const action = m_mixer.clipAction(clip)
    action.play()
  }

  return m_mixer
}

function animate() {
  requestAnimationFrame(animate)

  // 更新动画帧
  if (mixer) {
    mixer.update(clock.getDelta())
  }

  renderer.render(scene, camera)
}

onMounted(() => {
  init()
  loadModel()
  animate()
})
</script>
